package kz.pompei.ifacer.grot.acts;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import kz.pompei.ifacer.model.widgets.Widget;

public class ChangeFieldsAct extends Act {
  private final UUID uuid;
  
  private class ChangeElement {
    final String fieldName;
    Object newValue;
    Object oldValue;
    
    public ChangeElement(String fieldName, Object newValue) {
      this.fieldName = fieldName;
      this.newValue = newValue;
    }
    
    public void apply(Widget widget) throws Exception {
      Field field = widget.getClass().getField(fieldName);
      oldValue = field.get(widget);
      field.set(widget, newValue);
    }
    
    public void cancel(Widget widget) throws Exception {
      Field field = widget.getClass().getField(fieldName);
      field.set(widget, oldValue);
    }
    
    @Override
    public String toString() {
      return "(" + fieldName + ": " + oldValue + " -> " + newValue + ")";
    }
  }
  
  private final List<ChangeElement> list = new ArrayList<>();
  
  public ChangeFieldsAct(UUID uuid, Object... nameValuePairs) {
    this.uuid = uuid;
    
    if (nameValuePairs.length % 2 != 0) {
      throw new IllegalArgumentException("nameValuePairs.length % 2 = "
          + (nameValuePairs.length % 2));
    }
    
    for (int i = 0, C = nameValuePairs.length / 2; i < C; i++) {
      list.add(new ChangeElement((String)nameValuePairs[2 * i + 0], nameValuePairs[2 * i + 1]));
    }
  }
  
  @Override
  public void apply(GrotStorage grotStorage) throws Exception {
    Widget widget = grotStorage.getWidgetByUuid(uuid);
    for (ChangeElement ce : list) {
      ce.apply(widget);
    }
  }
  
  @Override
  public void cancel(GrotStorage grotStorage) throws Exception {
    Widget widget = grotStorage.getWidgetByUuid(uuid);
    for (int i = list.size(); i-- > 0;) {
      list.get(i).cancel(widget);
    }
  }
  
  @Override
  public String toString() {
    return "ChangeFieldsAct [" + uuid + " - " + list + "]";
  }
  
  private Set<String> fieldNameSet() {
    Set<String> ret = new HashSet<>();
    for (ChangeElement e : list) {
      ret.add(e.fieldName);
    }
    return ret;
  }
  
  @Override
  public boolean canMerge(Act act) {
    if (!(act instanceof ChangeFieldsAct)) return false;
    ChangeFieldsAct a = (ChangeFieldsAct)act;
    if (!uuid.equals(a.uuid)) return false;
    return fieldNameSet().equals(a.fieldNameSet());
  }
  
  private Map<String, ChangeElement> map() {
    Map<String, ChangeElement> ret = new HashMap<>();
    for (ChangeElement e : list) {
      ret.put(e.fieldName, e);
    }
    return ret;
  }
  
  @Override
  public void mergeAndApply(Act act, GrotStorage storage) throws Exception {
    ChangeFieldsAct a = (ChangeFieldsAct)act;
    a.apply(storage);
    
    Map<String, ChangeElement> map = a.map();
    for (ChangeElement ce : list) {
      ChangeElement anotherCE = map.get(ce.fieldName);
      ce.newValue = anotherCE.newValue;
    }
  }
}
